/*
 ============================================================================
 Name        : DerivedTypeEx.c
 Author      : mattia
 Version     :
 Copyright   : Your copyright notice
 Description : Compute Pi in MPI C++
 ============================================================================
 */

#include <stdio.h>
#include <openmpi/mpi.h>
#include <pthread.h>
#include "DppmParams.h"
#include "Utils.h"
#include "LoadBalancing.h"
#include "BestSolutionExchange.h"
#include "TuplesProcessing.h"
#include "Tuple.h"

#define MASTER 0
#define PRINTMASTER 1

int rank, size;
DppmParams * dppm;
SortedBuffer * tuples;
pthread_mutex_t tuples_mutex = PTHREAD_MUTEX_INITIALIZER;
Tuple * bestSolution;
pthread_mutex_t best_solution_mutex = PTHREAD_MUTEX_INITIALIZER;
bool end = false;
pthread_mutex_t end_flag_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t idle_cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_t safe_mpi_mutex = PTHREAD_MUTEX_INITIALIZER;

MPI_Comm bestSolutionComm;
MPI_Comm balancingComm;

int main(int argc, char** argv) {

	int numAct, T;
	int* numPreds;
	int* numModes;
	int** Preds;
	int** ModesCosts;
	int** ModesTimes;

	MPI_Init(&argc, &argv);
//	int required;
//	MPI_Init_thread(&argc,&argv,MPI_THREAD_MULTIPLE,&required);
//	if(required < MPI_THREAD_MULTIPLE){
//		cout<<"ERROR WITH MPI_THREAD_MULTIPLE!!!!"<<endl;
//		MPI_Abort(MPI_COMM_WORLD,1);
//		MPI_Finalize();
//		exit(-1);
//	}

	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);

	MPI_Comm_dup(MPI_COMM_WORLD, &bestSolutionComm);
	MPI_Comm_dup(MPI_COMM_WORLD, &balancingComm);

	if (!rank) {

		if ((argc != 2)
				|| readDppmSpec(argv[1], numAct, T, numPreds, Preds, numModes,
						ModesCosts, ModesTimes)) {
			printf("Error!!\n");
			return -1;
		}
//		cout<<"Leido DppmParams info"<<endl;
//		cout<<"Enviando DppmParams info"<<endl;
	} else {
		//cout<<"Recibiendo DppmParams info"<<endl;
	}

	MPI_Bcast(&numAct, 1, MPI_INT, MASTER, MPI_COMM_WORLD);
	MPI_Bcast(&T, 1, MPI_INT, MASTER, MPI_COMM_WORLD);
	if (rank) {
		numPreds = new int[numAct];
		numModes = new int[numAct];
		Preds = new int*[numAct];
		ModesCosts = new int*[numAct];
		ModesTimes = new int*[numAct];
	}
	MPI_Bcast(numPreds, numAct, MPI_INT, MASTER, MPI_COMM_WORLD);
	MPI_Bcast(numModes, numAct, MPI_INT, MASTER, MPI_COMM_WORLD);

	for (int i = 0; i < numAct; i++) {
		if (rank) {
			Preds[i] = new int[numPreds[i]];
			ModesCosts[i] = new int[numModes[i]];
			ModesTimes[i] = new int[numModes[i]];
		}
		MPI_Bcast(Preds[i], numPreds[i], MPI_INT, MASTER, MPI_COMM_WORLD);
		MPI_Bcast(ModesCosts[i], numModes[i], MPI_INT, MASTER, MPI_COMM_WORLD);
		MPI_Bcast(ModesTimes[i], numModes[i], MPI_INT, MASTER, MPI_COMM_WORLD);
	}

	Tuple::SetTuplesIntSize(numAct);

	bestSolution = new Tuple(numAct);

	tuples = new SortedBuffer();

	dppm = new DppmParams(numAct, T, numPreds, Preds, numModes, ModesCosts,
			ModesTimes);

	//inicializar tuples con los primeros prefijos
	if (!rank) {
		int numModesAct0 = dppm->getActivityNumModes(0);
		int * costsAct0 = dppm->getActivityModesCosts(0);
		int * timesAct0 = dppm->getActivityModesTimes(0);
		Tuple * tuple;

		for (int i = 0; i < numModesAct0; i++) {
			tuple = new Tuple(numAct);

			tuple->sigAct();
			tuple->time(timesAct0[i]);
			tuple->cost(costsAct0[i]);
			tuple->times(0, timesAct0[i]);
			tuple->modes(0, i);

			tuples->push(tuple);
		}
	}

	cout << "Creado DppmParams, rank = " << rank << endl;

	pthread_t loadBalancingThread = createThread(&LoadBalancing);
	pthread_t bestSolutionExchangeThread = createThread(&BestSolutionExchange);
	pthread_t tuplesProcessingThread = createThread(&TuplesProcessing);

//    cout<<"Prontos los hilos de ejecucion, rank = "<<rank<<endl;

	pthread_join(loadBalancingThread, NULL);
	pthread_join(bestSolutionExchangeThread, NULL);
	pthread_join(tuplesProcessingThread, NULL);

	cout << "Esperando join, rank = " << rank << endl;

	MPI_Comm_free(&bestSolutionComm);
	MPI_Comm_free(&balancingComm);


	int recvBuff[size][Tuple::tuplesIntSize];
	int recvBuffAux[size * Tuple::tuplesIntSize];
	//Compartir soluciones con un Gather al root
	//MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype sendtype,
	//void *recvbuf, int recvcount, MPI_Datatype recvtype, int root,
	//MPI_Comm comm)

	MPI_Gather(bestSolution->getArray(), Tuple::tuplesIntSize,
			MPI_INT, recvBuffAux, Tuple::tuplesIntSize,
			MPI_INT, 0, MPI_COMM_WORLD);

	int j = -1;
	for(int i = 0 ; i < (Tuple::tuplesIntSize * size) ; i++){
		if( i % Tuple::tuplesIntSize == 0 ){ j++; }
		recvBuff[j][i % Tuple::tuplesIntSize] = recvBuffAux[i];
	}

	if (!rank) {
		cout<<"DECIDO LA MEJOR SOLUCION!!"<<endl;
		//Decido la mejor solucion
		int BSIndex = 0;
		int BSCost = bestSolution->cost();

		for (int i = 1; i < size; i++) {
			if (Tuple::GetCostFromTupleArray(recvBuff[i]) < BSCost) {
				BSCost = Tuple::GetCostFromTupleArray(recvBuff[i]);
				BSIndex = i;
			}
		}

		if (BSIndex != rank) {
			delete bestSolution;
			bestSolution = new Tuple(Tuple::tuplesIntSize,
					recvBuff[BSIndex]);
		}

		printf("La mejor solución es:");
		bestSolution->print();

	}

	MPI_Finalize();

	delete dppm;
	delete tuples;
	delete bestSolution;

	return 0;
}

